// Copyright (C) Calum Grant 2008

#include <cppscript>
#include "unit_tests.hpp"
#include <sstream>


void test_default_container(var v)
{
	assert_throws( not_supported, var x = v[12] );
	assert_throws( not_supported, v[12] = 12 );
	assert( !v.contains(123) );
	assert( v.size() == 0 );
	assert( v.empty() );
	assert( v.begin() == v.end() );
	assert( v.rbegin() == v.rend() );
	assert_throws( not_supported, v.resize(1) );
	assert( v.keys() == var() );
	assert( v.values() == var() );
	assert_throws( not_supported, v.push_back(1) );
	assert_throws( not_supported, v.push_front(1) );
	assert_throws( not_supported, v.back() );
	assert_throws( not_supported, v.front() );
	assert_throws( not_supported, v.clear() );
	v.erase(0);
	assert_throws( not_supported, v.mixin(var()) );
	assert( !v.enumerator() );
	assert( !v.reverse_enumerator() );

	assert_throws( not_supported, v.impl().member_inc("") );
	assert_throws( not_supported, v.impl().member_inc("", 0) );
	assert_throws( not_supported, v.impl().member_dec("") );
	assert_throws( not_supported, v.impl().member_dec("", 0) );

	assert_throws( not_supported, v.impl().member_add("", 1) );
	assert_throws( not_supported, v.impl().member_sub("", 1) );

	assert_throws( not_supported, v.impl().member_call("") );

	v.impl().reserve(10);
	assert( v.impl().capacity() == 0 );
}


void test_default_iterator(var v)
{
	assert_throws( not_supported, *v );
	assert_throws( not_supported, v.key() );
	assert_throws( not_supported, v.value() );
	assert_throws( not_supported, ++v );
	assert_throws( not_supported, --v );
	assert_throws( not_supported, v++ );
	assert_throws( not_supported, v-- );
}


void test_default_assignment(var v)
{
	assert_throws( not_supported, v+=4 );
	assert_throws( not_supported, v-=4 );
	assert_throws( not_supported, v*=4 );
	assert_throws( not_supported, v/=4 );
	assert_throws( not_supported, v%=4 );
	assert_throws( not_supported, v<<=4 );
	assert_throws( not_supported, v>>=4 );
	assert_throws( not_supported, v&=4 );
	assert_throws( not_supported, v|=4 );
	assert_throws( not_supported, v^=4 );
}


void test_default_operators(var str)
{
	assert_throws( not_supported, str+1 );
	assert_throws( not_supported, str-1 );
	assert_throws( not_supported, str*1 );
	assert_throws( not_supported, str/1 );
	assert_throws( not_supported, str%1 );
	assert_throws( not_supported, -str );
	assert_throws( not_supported, str<<1 );
	assert_throws( not_supported, str>>1 );
	assert_throws( not_supported, str&1 );
	assert_throws( not_supported, str|1 );
	assert_throws( not_supported, str^1 );
	assert_throws( not_supported, ~str );
}


void test_null()
{
	// Basics
	assert( !var().impl().shared_var() );
	assert( null == var() );
	assert( var() != 3 );
	assert( var() < false );
	assert( !(var()<var()) );
	assert( var().clone() == var() );
	assert( +var() == var() );
	assert( var() == unpickle(pickle(var())) );
	assert( var().class_name() == "null" );
	assert( var().impl().comparison_index() == cmp_null );
	assert_throws( not_supported, null() );
	assert( null.max_args() == -1 );

	std::pair<std::size_t, std::size_t> r;
	assert_not_supported( null.impl().get_range(10,r) );

	assert( null.proxy().as_string() == "null" );

	// Conversions
	assert( var().as_int() == 0 );
	assert( var().as_double() == 0.0 );
	assert( var().as_string() == "null" );
	assert( var().as_wstring() == L"null" );
	assert( !var().as_bool() );
	assert( !var() );
	assert( !var().impl().as_ptr() );

	// Container
	test_default_container( var() );

	// Iterator
	test_default_iterator( var() );

	// Operators
	assert( var() + 1 == 1 );
	assert( var() - 1 == -1 );
	assert( var() * 12 == var() );
	assert( var() / 12 == var() );
	assert( -var() == var() );
	assert( var() % 12 == var() );
	assert( var() << 12 == var() );
	assertx( var() >> 2 == var());
	assert( var() | 4 == 4 );
	assert( (var() & 2) == var() );
	assert( var() ^ 3 == 3 );
	assert_throws( not_supported, ~var() );

	// Assignment
	test_default_assignment( var() );
}


void test_bool()
{
	// Construction
	var t=true, f=false;
	assert( var(true) == true );
	assert( var(false) == false );

	// Basics
	assert( !t.impl().shared_var() );
	assert( t.class_name() == "bool" );
	assert( f < t );
	assert( t < 1 );
	assert( t != 2 );
	assert( f != 0 );
	assert( t == +t );
	assert( f == +f );
	assert_throws( not_supported, t() );
	assert( t.max_args() == -1 );
	assert( t == unpickle(pickle(t)) );
	assert( f == unpickle(pickle(f)) );
	assert( f.impl().comparison_index() == cmp_false );
	assert( t.impl().comparison_index() == cmp_true );

	// Conversions
	assert( var(true) );
	assert( !var(false) );
	assert( t.as_int() == 1 );
	assert( f.as_int() == 0 );
	assert( t.as_double() == 1.0 );
	assert( f.as_double() == 0.0 );
	assert( t.as_string() == "true" );
	assert( f.as_string() == "false" );
	assert( t.as_wstring() == L"true" );
	assert( f.as_wstring() == L"false" );
	assert( !t.impl().as_ptr() );

	test_default_container( true );
	test_default_container( false );
	test_default_iterator( true );
	test_default_assignment( true );

	// Operators
	assert( t == t+f );
	assert_throws( not_supported, t-f );
	assert( f == t*f );
	assert_throws( not_supported, f/t );
	assert_throws( not_supported, f%t );
	assert_throws( not_supported, -t );
	assert_throws( not_supported, t<<1 );
	assert_throws( not_supported, t>>1 );
	assert( t|f );
	assert( !(f|f) );
	assert( t&t );
	assert( !(f&t) );
	assert( t^f );
	assert( !(t^t) );
	assert( !(f^f) );
	assert( ~f );
	assert( !~t );
}


void test_ch()
{
	// Constructor
	var c = 'a';
	var w = L'a';
	assert( var('a') == 'a' );
	assert( var(L'b') == L'b' );

	// Basics
	assert( c.class_name() == "char" );
	assert( w.class_name() == "char" );
	assert( c == +c );
	assert( w == +w );
	assert( unpickle(pickle(w)) == w );
	assert( unpickle(pickle(c)) == c );
	assert( unpickle(pickle(c)).as_string() == "a" );
	assert( c.max_args() == -1 );
	assert( w.max_args() == -1 );
	assert_throws( not_supported, c() );
	assert_throws( not_supported, w() );
	assert( c.impl().comparison_index() == cmp_char );
	assert( w.impl().comparison_index() == cmp_wchar );

	// Comparison
	assert( c == 'a' );
	assert( w == L'a' );
	assert( c < w );
	assert( c == c );
	assert( w == w );
	assert( w < L'b' );
	assert( c < 'c' );
	assert( var(char(020)) == '\020' );
	assert( var('a') < var('b') );

	// Conversion
	assert( c.as_int() == 'a' );
	assert( w.as_int() == L'a' );
	assert( c.as_string() == "a" );
	assert( w.as_wstring() == L"a" );
	assert( c.as_double() == 'a' );
	assert( w.as_double() == 'a' );
	assert( c );
	assert( !var('\0') );
	assert( !c.impl().as_ptr() );

	// Container
	test_default_container( c );
	test_default_container( w );

	// Iterator
	assert_throws( not_supported, c.key() );
	assert_throws( not_supported, c.value() );
	assert_throws( not_supported, *c );

	assert( ++c == 'b' );
	assert( --c == 'a' );

	// Operators
	assert( (c+3).class_name() == "char" );
	assert( (c+3).as_string() == "d" );
	assert( (var('z')-2).as_string() == "x" );
	assert( -c == -'a' );
	assert( c*2 == 'a'*2 );
	assert( c/2 == 'a'/2 );
	assert( c%2 == 'a'%2 );
	assert( c>>1 == 'a'>>1 );
	assert( c<<1 == 'a'<<1 );
	assert( (c&15) == ('a'&15) );
	assert( (c|15) == ('a'|15) );
	assert( (c^15) == ('a'^15) );
	assert( ~c == ~'a' );

	// Assignment
	c+=2; assert( c == 'c' );
	c-=2; assert( c == 'a' );
	c*=3; assert( c.as_int() == 'a'*3 );
	c/=3; assert( c == 'a' );
	c %= 'a'; assert( c == '\0' );
	c = 'a'; c<<=1; assert( c.as_int() == 'a'<<1 );
	c>>=1; assert( c == 'a' );
	c&=123; assert( c.as_int() == ('a' & 123) );
	c='a'; c|=12; assert( c.as_int() == ('a'|12) );
	c='a'; c^=15; assert( c.as_int() == ('a'^15) );
}


void test_int()
{
	// Construction
	assert( var(12) == 12 );

	// Basics
	assert( !var(5).impl().shared_var() );
	assert( var(5).class_name() == "int" );
	assert( +var(1) == 1 );
	assert_throws( not_supported, var(1)() );
	assert( unpickle(pickle(4)) == 4 );
	assert( unpickle(pickle(-3)) == -3 );
	var tmp = object();
	tmp["x"] = var(1);
	assert( tmp["x"] == 1 );
	assert( var(12).max_args() == -1 );
	assert_throws( not_supported, var(12)() );

	// Comparison
	assert( var(1).impl().comparison_index() == cmp_int );
	assert( var(5) < 6 );
	assert( var(true) < var(0) );
	assert( var(1) != 2 );
	assert( var(1) <= 2 );
	assert( var(1) < 2 );
	assert( var(2) > 1 );
	assert( var(2) >= 1 );
	assert( var(1) <= 1 );
	assert( var(1) >= 1 );

	// Conversions
	assert( var(12).as_string() == "12" );
	assert( var(-2).as_string() == "-2" );
	assert( var(12).as_wstring() == L"12" );
	assert( var(-2).as_wstring() == L"-2" );
	assert( var(2).as_double() == 2.0 );
	assert( var(1) );
	assert( !var(0) );
	assert( !var(1).impl().as_ptr() );

	test_default_container( 1 );

	assert_throws( not_supported, *var(1) );
	assert_throws( not_supported, var(1).key() );
	assert_throws( not_supported, var(1).value() );

	// Operators
	assert( var(12) + 4 == 16 );
	assert( var(12) + "1" == 13 );
	assert( var(10) - 3 == 7 );
	assert( var(10) * 2 == 20 );
	assert( var(10) / 5 == 2 );
	assert( var(10) % 3 == 1 );

	// The ++ operator increments
	var i=5;
	assert( ++i == 6 );

	// The -- operator decrements
	assert( --i == 5 );
	
	// Postincrement & postdecrement
	assert( i++ == 5 && i == 6 );
	assert( i-- == 6 && i == 5 );

	// The bitwise operators: | & ^ ~
	assert( var(1)|2==3 );
	assert( var(3)&2==2 );
	assert( var(3)^1 == 2 );
	assert( ~var(1)==~1 );

	// The shift operators
	assert( var(1) << "2" == 4 );
	assert( var(4) >> 1 == 2 );

	// The unary operators
	assert( +var(1) == 1 );
	assert( -var(2) == -2 );

	// Assignment operators += -= *= /= %= >>= <<= |= ||= &= &&= 
	var x=0;
	assert( (x+=2) == 2 );
	assert( (x-=1) == 1 );
	assert( (x*=6) == 6 );
	assert( (x/=3) == 2 );
	assert( (x%=2) == 0 );
	x+=1;
	x<<=10;
	assert( x == 1024 );
	x>>=9;
	assert( x == 2 );
	x|=1;
	assert( x == 3 );
	x&=2;
	assert( x == 2 );
}


void test_double()
{
	// Construction
	var x = 0.5;
	var y = 1.0;

	// Basics
	assert( x.class_name() == "double" );
	assert( x.impl().comparison_index() == cmp_double );
	assert( x == +x );
	assert( unpickle(pickle(x)) == x );
	assert_throws( not_supported, x() );
	assert( x.max_args() == -1 );
	
	// Comparison
	assert( x < 1.0 );
	assert( x > 0.25 );
	assert( x > 12 );

	// Conversion
	assert( y.as_int() == 1 );
	assert( x.as_string() == "0.5" );
	assert( x.as_double() == 0.5 );
	assert( x );
	assert( !var(0.0).as_bool() );

	test_default_container( x );

	// Iterator
	assert_throws( not_supported, x.key() );
	assert_throws( not_supported, x.value() );
	assert_throws( not_supported, *x );

	assert( x+0.5 == 1.0 );
	assert( x+"0.5" == 1.0 );
	assert( x - 1 == -0.5 );
	assert( x - "1" == -0.5 );
	assert( x * "0.5" == 0.25 );
	assert( x / "0.5" == 1.0 );
	assert_throws( not_supported, x % 1 );
	assert_throws( not_supported, x >> 1 );
	assert_throws( not_supported, x << 1 );
	assert_throws( not_supported, x & 1 );
	assert_throws( not_supported, x | 1 );
	assert_throws( not_supported, x ^ 1 );
	assert_throws( not_supported, ~x );

	assert( ++x == 1.5 );
	assert( --x == 0.5 );
	assert( +var(2.0) == 2.0 );
	assert( -var(2.0) == -2.0 );

	var d = 1.0;
	d+=1;
	assert( d == 2.0 );
	d-=1;
	assert( d == 1.0 );
	d*=6;
	assert( d == 6.0 );
	d/=2;
	assert( d == 3.0 );
	
	assert_throws( not_supported, x %= 2 );
	assert_throws( not_supported, x <<= 2 );
	assert_throws( not_supported, x >>= 2 );
	assert_throws( not_supported, x &= 2 );
	assert_throws( not_supported, x |= 2 );
	assert_throws( not_supported, x ^= 2 );
}


void test_string()
{
	// Comparison
	assert( var() == var() );
	assert( var() < 12 );

	// A string is created by assigning a char* to it
	var i = "abc";

	// The original string can be extracted using var::as_string() method
	assert( i.as_string() == "abc" );

	// This also works for std::string
	var j = std::string("def");
	assert( j.as_string() == "def");

	// A wide pointer will create a wide string
	var k = L"abc";
	assert( k.as_wstring() == L"abc" );

	// The function create_string() also creates a string.
	assert( internal::create_string("123") == "123" );

	// Assignment to string
	var s;
	s = "123";
	assert( s == "123" );

	// You can also convert wide strings to narrow strings implicitly,
	// though you are better off just sticking to one string type.
	assert( k.as_string() == "abc" );
	assert( k != i );
	assert( i != k );

	// The + operator concatenates two strings
	assert( var("ab") + "cd" == "abcd" );

	// The * operator repeats a string
	assert( var("abc") * 0 == "" );
	assert( var("abc") * 2 == "abcabc" );

	// Comparison performs a lexographical, case sensitive compare
	assert( var("abc") < "def" );
	assert( var("") < i );

	// An empty string is false
	assert( !var("") );

	// A non-empty string is true
	assert( var("abc") );

	// Conversion to an int parses the number
	assert( var("abc").as_int() == 0 );
	assert( var(L"2").as_int() == 2 );

	// Conversion to a double parses it
	assert( var("0.5").as_double() == 0.5 );
	assert( var("bbc").as_double() == 0.0 );

	// resize
	var abcd("abcd");
	abcd.resize(2);
	assert( abcd == "ab" );
	abcd.resize(4);
	assert( abcd == "ab  " );

	// +=
	assert( (abcd += "xy") == "ab  xy" );

	// Clone
	var s1="hello", s2= +s1;
	s1[0] = 'x';
	assert(s2 == "hello");
}
